﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using AmfMessage = WcfFlashRemoting.ServiceModel.Channels.AmfMessage;
using WcfFlashRemoting.ServiceModel.Channels;
using WcfFlashRemoting.AmfObjects;

namespace WcfFlashRemoting.ServiceModel.Dispatcher
{
    class AmfClientFormatter : System.ServiceModel.Dispatcher.IClientMessageFormatter
    {
        Dictionary<string, Type> knownTypes;
        OperationDescription opDesc;
        public AmfClientFormatter(OperationDescription opDesc)
        {
            this.opDesc = opDesc;
            knownTypes = opDesc.KnownTypes.ToDictionary(type => TypeHelper.GetContractName(type));
        }

        #region IClientMessageFormatter Members

        public object DeserializeReply(System.ServiceModel.Channels.Message message, object[] parameters)
        {
            AmfBody body = (message as AmfMultipleMessage).AmfBodies[0];
            MessageDescription msgDesc = opDesc.Messages.Where(m => m.Direction == MessageDirection.Output).First();
            if (body.Content == null)
            {
                return null;
            }

            if (body.Target.EndsWith("onResult"))
            {

                if (msgDesc.MessageType == null)
                {
                    return DecodeDataContract(body, msgDesc, parameters);
                }
                else
                {
                    return DecodeMessageContract((message as AmfMultipleMessage).AmfHeaders, body, msgDesc, parameters);
                }
            }
            else
            {
                // a fault!
                NamedDictionary namedData = body.Content as NamedDictionary;

                Type detailType = opDesc.Faults.First(fault => fault.Name == namedData.Properties["type"] as string).DetailType;
                object details = TypeHelper.Instantiate(detailType, namedData.Properties["details"], knownTypes);
                Type exceptionType = typeof(System.ServiceModel.FaultException<>).MakeGenericType(detailType);
                throw Activator.CreateInstance(exceptionType, details, 
                    new System.ServiceModel.FaultReason(namedData.Properties["description"] as string),
                    new System.ServiceModel.FaultCode(namedData.Properties["code"] as string)) as Exception;
            }
        }

        private object DecodeDataContract(AmfBody body, MessageDescription msgDesc, object[] parameters)
        {
            object result = null;
            if (body.Content is object[])
            {
                object[] data = body.Content as object[];
                int offset = 0;
                if (msgDesc.Body.ReturnValue != null)
                {
                    offset = 1;
                    result = TypeHelper.Instantiate(msgDesc.Body.ReturnValue.Type, data[0], knownTypes);
                }
                for (int i = offset; i < data.Length; i++)
                {
                    parameters[i - offset] = TypeHelper.Instantiate(msgDesc.Body.Parts[i - offset].Type, data[i], knownTypes);
                }
            }
            else
            {
                NamedDictionary namedData = body.Content as NamedDictionary;
                foreach (KeyValuePair<string, object> kvp in namedData.Properties)
                {
                    MessagePartDescription part = msgDesc.Body.Parts.First(p => p.Name == kvp.Key);
                    int index = msgDesc.Body.Parts.IndexOf(part);
                    parameters[index] = TypeHelper.Instantiate(part.Type, kvp.Value, knownTypes);
                }
            }
            return result;
        }

        private object DecodeMessageContract(AmfHeader[] headers, AmfBody body, MessageDescription msgDesc, object[] parameters)
        {
            object result = System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject(msgDesc.MessageType);
            if (body.Content is object[])
            {
                object[] data = body.Content as object[];
                for (int i = 0; i < data.Length; i++)
                {
                    MessagePartDescription part = msgDesc.Body.Parts[i];
                    Set(result, part, data[i]);
                }
            }
            else
            {
                NamedDictionary namedData = body.Content as NamedDictionary;
                foreach (KeyValuePair<string, object> kvp in namedData.Properties)
                {
                    MessagePartDescription part = msgDesc.Body.Parts.First(p => p.Name == kvp.Key);
                    Set(result, part, kvp.Value);
                }
            }

            foreach (AmfHeader header in headers)
            {
                MessagePartDescription part = msgDesc.Headers.FirstOrDefault(p => p.Name == header.Name);
                if (part != null)
                {
                    Set(result, part, header.Content);
                }
                else
                {
                    if (header.MustUnderstand)
                    {
                        throw new InvalidOperationException("Header '" + header.Name + "' not understood by endpoint.");
                    }
                }
            }
            return result;
        }

        private void Set(object instance, MessagePartDescription part, object p)
        {
            object value = TypeHelper.Instantiate(part.Type, p, knownTypes);
            if (part.MemberInfo is System.Reflection.PropertyInfo)
            {
                (part.MemberInfo as System.Reflection.PropertyInfo).SetValue(instance, value, null);
            }
            else
            {
                (part.MemberInfo as System.Reflection.FieldInfo).SetValue(instance, value);
            }
        }

        public System.ServiceModel.Channels.Message SerializeRequest(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters)
        {
            List<WcfFlashRemoting.AmfObjects.AmfHeader> headers = new List<WcfFlashRemoting.AmfObjects.AmfHeader>();

            WcfFlashRemoting.AmfObjects.AmfBody body = new WcfFlashRemoting.AmfObjects.AmfBody();
            body.Target = opDesc.Name;
            body.Response = Guid.NewGuid().ToString();

            if (parameters.Length > 0 && parameters[0] != null && parameters[0].GetType().GetCustomAttributes(typeof(System.ServiceModel.MessageContractAttribute), true).Length > 0)
            {
                body.Content = TypeHelper.GetBodyNamedDictionaryFor(parameters[0]);
                headers.AddRange(TypeHelper.GetHeadersFor(parameters[0]).Select(kvp => new WcfFlashRemoting.AmfObjects.AmfHeader() { Name = kvp.Key, Content = kvp.Value, MustUnderstand = false }));
            }
            else
            {
                List<object> content = new List<object>();
                content.AddRange(parameters);
                body.Content = content.ToArray();
            }
            WcfFlashRemoting.ServiceModel.Channels.AmfMessage result = new WcfFlashRemoting.ServiceModel.Channels.AmfMessage(messageVersion, headers.ToArray(), body, 3);
            //result.Headers.To = new Uri(opDesc.DeclaringContract.Namespace);
            return result;
        }

        #endregion
    }
}
